package com.code.monitor.handler;

import com.alibaba.fastjson2.JSON;
import com.code.monitor.constants.Constants;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketFrameDecoder;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.internal.AppendableCharSequence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.Charset;

/**
 * 根据连接内容区分不同的协议信息
 *
 * @author ggh
 * @date 2023/5/8
 */
public class ProtocolAnalyHandler extends ChannelInboundHandlerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(ProtocolAnalyHandler.class);
    private SerializableMessageCodec serializableMessageCodec = null;
    private MessageHandler messageHandler = null;
    private HttpMessageHandler httpMessageHandler = null;
    private WebSocketConnectHandler webSocketConnectHandler = null;
    private WebSocketMessageHandler webSocketMessageHandler = null;

    private String webSocketPath = null;

    public ProtocolAnalyHandler(SerializableMessageCodec serializableMessageCodec, MessageHandler messageHandler, HttpMessageHandler httpMessageHandler, WebSocketConnectHandler webSocketConnectHandler, WebSocketMessageHandler webSocketMessageHandler, String webSocketPath) {
        this.serializableMessageCodec = serializableMessageCodec;
        this.messageHandler = messageHandler;
        this.httpMessageHandler = httpMessageHandler;
        this.webSocketConnectHandler = webSocketConnectHandler;
        this.webSocketMessageHandler = webSocketMessageHandler;
        this.webSocketPath = webSocketPath;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;

        //如果是自定义协议
        if (isMessageProtocol(byteBuf)) {
            ChannelHandlerContext context = ctx.pipeline().context(LengthFieldBasedFrameDecoder.class);
            // 防止多次放入对象
            if (context == null) {
                ctx.pipeline().addLast(new LengthFieldBasedFrameDecoder(8192, 8, 4));
                ctx.pipeline().addLast(serializableMessageCodec);
                ctx.pipeline().addLast(messageHandler);
            }
        } else {
            ChannelHandlerContext context = ctx.pipeline().context(WebSocketFrameDecoder.class);
            if (context != null) {
                ctx.fireChannelRead(msg);
                return;
            }

            AppendableCharSequence seq = parseRequest(byteBuf);
            String[] initialLine = splitInitialLine(seq);
            if (seq.length() < 100) {
                logger.debug(JSON.toJSONString(initialLine));
            }
            if (initialLine == null || initialLine.length < 3 || !Constants.HTTP_METHODS.contains(initialLine[0])) {
                // 存在请求http请求或者过大没有完整接收到情况
//                logger.info("其他请求:{}", JSON.toJSONString(initialLine));
                byteBuf.readerIndex(0);
            } else if (initialLine[1].startsWith(webSocketPath)) {
                logger.info("websocket请求");
                //如果是webSocket协议
                ctx.pipeline().addLast(new HttpServerCodec());
                ctx.pipeline().addLast(new HttpObjectAggregator(8192));
                ctx.pipeline().addLast(new ChunkedWriteHandler());
                ctx.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath));
                ctx.pipeline().addLast(webSocketConnectHandler);
                ctx.pipeline().addLast(webSocketMessageHandler);
            } else {
                logger.info("http请求");
                //如果是http请求
                ctx.pipeline().addLast(new HttpServerCodec(4096, 8192, 4 * 1024 * 1024));
                ctx.pipeline().addLast(new HttpObjectAggregator(100 * 1024 * 1024));
                ctx.pipeline().addLast(new CorsHandler());
                ctx.pipeline().addLast(new ChunkedWriteHandler());
                ctx.pipeline().addLast(httpMessageHandler);
            }
        }

        super.channelRead(ctx, msg);
    }

    public boolean isMessageProtocol(ByteBuf byteBuf) {
        String revMagicNum = String.valueOf(byteBuf.getCharSequence(0, Constants.MAGIC_NUM.length(), Charset.defaultCharset()));
        if (Constants.MAGIC_NUM.equals(revMagicNum)) {
            return true;
        }
        return false;
    }

    public AppendableCharSequence parseRequest(ByteBuf byteBuf) {
        if (!skipControlCharacters(byteBuf)) {
            return null;
        }

        AppendableCharSequence seq = new AppendableCharSequence(128);
        for (int i = byteBuf.readerIndex(); i < byteBuf.writerIndex(); i++) {
            byte value = byteBuf.getByte(i);
            char nextByte = (char) (value & 0xFF);
            if (nextByte == HttpConstants.CR) {
                break;
            }
            if (nextByte == HttpConstants.LF) {
                break;
            }

            seq.append(nextByte);
        }

        return seq;
    }

    private boolean skipControlCharacters(ByteBuf buffer) {
        boolean skiped = false;
        final int wIdx = buffer.writerIndex();
        int rIdx = buffer.readerIndex();
        while (wIdx > rIdx) {
            int c = buffer.getUnsignedByte(rIdx++);
            if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
                rIdx--;
                skiped = true;
                break;
            }
        }
        buffer.readerIndex(rIdx);
        return skiped;
    }

    private String[] splitInitialLine(AppendableCharSequence sb) {
        if (sb == null) {
            return null;
        }
        int aStart;
        int aEnd;
        int bStart;
        int bEnd;
        int cStart;
        int cEnd;

        aStart = findNonWhitespace(sb, 0);
        aEnd = findWhitespace(sb, aStart);

        bStart = findNonWhitespace(sb, aEnd);
        bEnd = findWhitespace(sb, bStart);

        cStart = findNonWhitespace(sb, bEnd);
        cEnd = findEndOfString(sb);

        return new String[]{
                sb.subStringUnsafe(aStart, aEnd),
                sb.subStringUnsafe(bStart, bEnd),
                cStart < cEnd ? sb.subStringUnsafe(cStart, cEnd) : ""};
    }

    private int findNonWhitespace(AppendableCharSequence sb, int offset) {
        for (int result = offset; result < sb.length(); ++result) {
            if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
                return result;
            }
        }
        return sb.length();
    }

    private int findWhitespace(AppendableCharSequence sb, int offset) {
        for (int result = offset; result < sb.length(); ++result) {
            if (Character.isWhitespace(sb.charAtUnsafe(result))) {
                return result;
            }
        }
        return sb.length();
    }

    private int findEndOfString(AppendableCharSequence sb) {
        for (int result = sb.length() - 1; result > 0; --result) {
            if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
                return result + 1;
            }
        }
        return 0;
    }
}
